-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Java: Promote PathSanitizer.qll from experimental
#10177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
e9a6538 to
728cd65
Compare
lcartey
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provided some feedback on what I think we should consider a valid sanitizer for these queries.
| private predicate isStringPartialMatch(MethodAccess ma) { | ||
| ma.getMethod().getDeclaringType() instanceof TypeString and | ||
| ma.getMethod().getName() = | ||
| ["contains", "startsWith", "matches", "regionMatches", "indexOf", "lastIndexOf"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When used in the allowListGuard case, I don't think we should consider contains, indexOf and lastIndexOf as sufficient sanitizers, as they do not provide sufficient protection for absolute paths provided by the user (e.g. https://cwe.mitre.org/data/definitions/36.html).
Consider this example:
Path path = Paths.get("/etc/passwd");
if (!path.contains("..")) {
if (path.contains("mySpecialCase")) {
sink(path);
}
}This is considered an allow list sanitizer, but is clearly not sufficient for absolute paths.
For regionMatches, I think we should only allow it when it is a prefix (i.e. starting at index zero). We may also want to do some basic analysis of matches to consider if it starts with .* or similar, which would, again, allow absolute paths through.
I think these non-prefix cases are also problematic in the disallowListGuard case as well, because we don't consider what is being disallowed. In order to be an effective sanitizer I think we need to a least consider whether the standard path separators such as / and \ are being disallowed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are good suggestions, thanks! I'm actually modifying the logic of this sanitizer even further, so I'll try to fit these ideas in, too.
Keep in mind that, at some point, we need to compromise. Overfitting to one case may leave others out (either introducing FNs or FPs). For instance, in your example:
Path path = Paths.get("/etc/passwd");
if (!path.contains("..")) {
if (path.contains("mySpecialCase")) {
sink(path);
}
}The payload would not actually reach the sink because /etc/passwd doesn't contain mySpecialCase. I agree the payload would go through if it looked like /anotherDirectory/mySpecialCase/*, whereas the intention of the developer probably was trying to restrict the path to /mySpecialCase/*, but the attacker's options are way more limited in this case (it funnily starts to look like a potential partial path traversal instead).
Just saying this because some times contains et al might be enough to avoid exploitation — so not considering them would introduce FPs, but adding them could introduce FNs in other specific scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I chose a bad example for the allowListGuard, should have been something like this:
String path = "/etc/passwd";
if (!path.contains("..")) {
if (path.contains("pass")) {
sink(path);
}
}I agree the payload would go through if it looked like /anotherDirectory/mySpecialCase/, whereas the intention of the developer probably was trying to restrict the path to /mySpecialCase/, but the attacker's options are way more limited in this case (it funnily starts to look like a potential partial path traversal instead).
Although the contains may limit the attackers choice of full path, it doesn't prevent an attacker from creating a path with any directory prefix on the system[1]. That always seems undesirable and unintended and although it may be more difficult to exploit that a vanilla tainted path example, it's also hard to convince yourself that it's not exploitable because it depends on paths accessible to the process, software installed on the system and the exact set of allowed patterns.
Out of interest, do we have some examples of this type of allow list sanitizer?
Keep in mind that, at some point, we need to compromise. Overfitting to one case may leave others out (either introducing FNs or FPs).
fwiw, for this rule I think poor sanitizers that cause false negatives are more of a problem than good sanitizers that cause false positives.
[1]: Assuming the value used as a path is created in a way that's vulnerable to absolute path traversal e.g. new File(taintedValue); and not new File(parent, taintedValue). In an ideal world I think we would actually separate absolute and relative path traversal with flow states, and only consider the latter for relative path traversal, but I think that's probably a separate issue. 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fwiw, for this rule I think poor sanitizers that cause false negatives are more of a problem than good sanitizers that cause false positives.
You have more context from users, so let's follow your approach. See 60b31c6: AllowListGuard now only considers prefix checks (and not partial matching checks), and regionMatches and matches apply heuristics to make sure a prefix is being checked.
See the other thread below for the analogous changes in BlockListGuard.
In an ideal world I think we would actually separate absolute and relative path traversal with flow states, and only consider the latter for relative path traversal, but I think that's probably a separate issue. 🙂
This sounds like a good improvement to do in a future PR. Noted, thanks!
|
|
||
| private class BlockListSanitizer extends PathTraversalSanitizer { | ||
| private class BlockListSanitizer extends PathInjectionSanitizer { | ||
| BlockListSanitizer() { this = DataFlow::BarrierGuard<blockListGuard/3>::getABarrierNode() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the blockListGuard we require that the user has mitigated for URL decoding issues. Is this really a universal requirement to use a block list?
As far as I know, the only impact URI decoding has on tainted path issues is that decoding should not occur after path checks, because the path may only be malicious after decoding.
However, requiring the user decode the path before checking can be outright harmful if using a library or framework that already URI decodes remote flow sources, or for remote flow sources that don't require URI decoding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, block lists are less than ideal sanitizers, and allow lists should be preferred. One of the reasons for that is that it's easy to forget about a bad case, or to not consider different encodings when checking the externally-provided data.
So this condition is there to force block lists approaches to be a little more robust if we want them to be considered valid sanitizers. I agree that the requirement of URL decoding prior to the check might be too much, but theoretically your examples:
if using a library or framework that already URI decodes remote flow sources, or for remote flow sources that don't require URI decoding.
could be fixed by marking those specific remote flow sources as UrlDecoderSanitizers themselves.
The alternative would be that a simple payload.contains("..") would be considered a sanitizer, and then users would complain as well, since we all know that that doesn't work at all. Again, at some point we need to trade FNs for FPs, or vice versa.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The alternative would be that a simple payload.contains("..") would be considered a sanitizer
I agree that we don't want that, but I don't think requiring a URI decoding step beforehand makes payload.contains("..") a sufficient sanitizer for absolute path traversal, for the same reasons as above.
could be fixed by marking those specific remote flow sources as UrlDecoderSanitizers themselves.
I don't think requiring URI decoding before the check prevents any security issues. The URI decoding issue is only a problem if you URI decode between the check and the access, e.g.
x = decode(source())
if (check(x)) {
new File(x); // fine
}
x = source()
if (check(x)) {
new File(x); // also fine, File won't URI decode
}
x = source()
if (check(x)) {
new File(decode(x)); // bad!
}
For example, in the Spring vulnerability the issue wasn't that they forgot to URI decode before their tainted path check. Instead, the problem was that they ran second decode step after the check, but before the value was used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clearly, requiring taking double-encoding into account only makes sense if the sink itself implicitly URL-decodes the value, which happens in some dispatch/forward/redirect vulnerabilities. I think this could actually be another interesting use case for flow states (see the comment I added in the now FN test in https://github.com/github/codeql/pull/10177/files#diff-6bc44c5765a6e460db430d2c13a69b22afe818b46bc96d630508b5e1d1cd6eb1R103-R106).
So, with that in mind, I followed your advice and removed the URL-decode requirement for blockListGuard. Instead, I added the same canonicalization/path traversal check requirement that allowListGuard has, assuming that the block list will check for dangerous prefixes (like /data in Android). The only case that would not need normalization would be contains(File.seperator) (or contains("/") or contains("\\")), but to special-case those bad words we would need to create yet another guard. We can discuss if you think that'd be worth it.
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,577,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,580,130,28,,,7,,,10
- Totals,,217,6438,1474,117,6,10,107,33,1,84
+ Totals,,217,6441,1474,117,6,10,107,33,1,84
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
|
@aschackmull I introduced a new parameterized module here. If you have the chance to review it, let me know if something doesn't look right. |
Had a quick look - looks fine to me, I think. Just make sure you have some test coverage for that - we may want to fold some of it into the BarrierGuard module at some point. |
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,577,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,581,130,28,,,7,,,10
- Totals,,217,6439,1474,117,6,10,107,33,1,84
+ Totals,,217,6443,1474,117,6,10,107,33,1,84
- java.io,37,,39,,15,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,39,
+ java.io,37,,40,,15,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,40,
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,577,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,581,130,28,,,7,,,10
- Totals,,217,6439,1474,117,6,10,107,33,1,84
+ Totals,,217,6443,1474,117,6,10,107,33,1,84
- java.io,37,,39,,15,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,39,
+ java.io,37,,40,,15,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,40,
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
|
Hey. Thanks for tackling this! It's a real improvement. I particularly like the tests as they serve as a kind of spec document. Would it make sense to rename |
|
Another thing: I'm struggling to understand the |
|
Hey @vlkl-sap, thanks for the feedback!
Hmm, that seems wrong indeed. Requiring prefix checks instead of substring checks is something that makes sense in the allow list approach, but for the block list approach substring checks work fine (actually, even better from a security POV since they cover more cases). Thanks for pointing this out, I'll fix it. |
|
Are these duplicates? codeql/java/ql/test/library-tests/pathsanitizer/Test.java Lines 252 to 265 in c5abbba
Or did I fail the eye test? |
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,585,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,589,130,28,,,7,,,10
- Totals,,217,6442,1476,119,6,10,107,33,1,84
+ Totals,,217,6446,1476,119,6,10,107,33,1,84
- java.io,37,,39,,15,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,39,
+ java.io,37,,40,,15,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,40,
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
Indeed they are! One of those two should have been the See the recent commits for fixes to all the issues you mentioned. Thanks again for your detailed review @vlkl-sap! |
|
ebd1aa6 to
c6f3e23
Compare
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,585,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,589,130,28,,,7,,,10
- Totals,,217,8428,1524,129,6,10,107,33,1,86
+ Totals,,217,8432,1524,129,6,10,107,33,1,86
- java.io,37,,39,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,39,
+ java.io,37,,40,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,40,
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
Thanks @yo-h! Conflict resolved. |
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,585,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,589,130,28,,,7,,,10
- Totals,,217,8428,1524,129,6,10,107,33,1,86
+ Totals,,217,8432,1524,129,6,10,107,33,1,86
- java.io,37,,39,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,39,
+ java.io,37,,40,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,40,
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
|
Looks like there's a performance problem somewhere. |
I've rebased main and run DCA again, to make sure that a) it wasn't a one-time thing and b) we benefit from your recent virtual dispatch changes (because I was seeing more path injection alerts, which is weird). Also I added the tuple-counting option this time. |
051ef0d to
2ae215a
Compare
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,585,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,589,130,28,,,7,,,10
- Totals,,217,8428,1524,129,6,10,107,33,1,86
+ Totals,,217,8432,1524,129,6,10,107,33,1,86
- java.io,37,,39,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,39,
+ java.io,37,,40,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,40,
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,585,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,589,130,28,,,7,,,10
- Totals,,217,8428,1524,129,6,10,107,33,1,86
+ Totals,,217,8432,1524,129,6,10,107,33,1,86
- java.io,37,,39,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,39,
+ java.io,37,,40,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,40,
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
Rename PathTraversalSanitizer to PathInjectionSanitizer
Apply code review suggestions regarding weak sanitizers
This is needed by PathSanitizer but also helps simplify ZipSlip.ql
A block list approach doesn't need to restrict itself to prefix matching
606a6b2 to
9db65ea
Compare
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,585,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,589,130,28,,,7,,,10
- Totals,,217,8428,1524,129,6,10,107,33,1,86
+ Totals,,217,8432,1524,129,6,10,107,33,1,86
- java.io,37,,39,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,39,
+ java.io,37,,40,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,40,
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
Click to show differences in coveragejavaGenerated file changes for java
- Java Standard Library,``java.*``,3,585,130,28,,,7,,,10
+ Java Standard Library,``java.*``,3,589,130,28,,,7,,,10
- Totals,,217,8428,1524,129,6,10,107,33,1,86
+ Totals,,217,8432,1524,129,6,10,107,33,1,86
- java.io,37,,39,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,39,
+ java.io,37,,40,,15,,,,,,,,,,,,,,,,,,,,,,,,,22,,,,,,,,40,
- java.nio,15,,11,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,11,
+ java.nio,15,,14,,13,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,14, |
|
DCA looks good now, with the exception of some increased tuple counts (I'm not sure we can do much about that?). There's some increase (and also some decrease) in some projects' path injection alerts, which seems logic — now we are accounting for incomplete path injection sanitization/validation attempts, so in some cases we'll now be flagging new results that we previously considered safe. |
This reverts commit 7b34b10.
Promotes
PathSanitizer.qllfrom experimental and uses it injava/tainted-path,java/tainted-path-localandjava/zipslip. The deprecation ofPathTraversalBarrierGuardwasn't necessary since it was previously in experimental and thus it was not importable from the main query pack, so I removed it.Also, applied the changes from #9956 into
java/tainted-path-localfor consistency.Note that the ZipSlip query already had a nice sanitizer in place, the features of which were merged into
PathSanitizer.qll.I recommend commit-by-commit review.